package nl.scalasim
package util

import scala.collection.mutable.ArrayBuffer
import java.io._
import java.util.zip.{GZIPInputStream, GZIPOutputStream}

/**
 * Class for holding all gamestate-changing actions in the correct order,
 * replaying these actions and saving/loading these actions.  
 *
 * @author Yigal Duppen
 * @since Nov 7, 2010
 */
sealed class GameStateAction
case object GoAction extends GameStateAction
case class AddAction(descriptorName: String, position: Position) extends GameStateAction


class ReplayableGameState extends Logging {

  private val history = new ArrayBuffer[GameStateAction]()

  /**
   * Resets the history.
   */
  def reset: Unit = {
    history.clear
  }

  /**
   * Records a single action.
   */
  def record(a: GameStateAction): Unit = {
    history += a
  }

  /**
   * Replays all stored actions in order
   * @param c the controller to replay with
   * @param progress a function taking as parameters the current action
   * and the total amount of actions. 
   */
  def replay(c: Controller, progress: Amount => Unit): Unit = {
    val size = history.size

    history.zipWithIndex foreach {
      item =>
        val action = item._1
        val index = item._2
        progress(Amount(index, size))
        action match {
          case GoAction => {
            c.tick()
          }
          case AddAction(s, p) => {
            c.setting.types.find(_.name == s) match {
              case None => {
                log.warning("Unknown actor: " + s)
              }
              case Some(descriptor) => {
                c.add(descriptor, p)
              }
            }
          }
          case a => {
            log.warning("Unknown action: " + a)
          }
        }
    }
  }

}


object ReplayableGameState {

  /**
   * Stores a game state into a file
   */
  def store(s: ReplayableGameState, f: File): Either[IOException, Unit] = {
    val out = new GZIPOutputStream(new BufferedOutputStream(new FileOutputStream(f)))
    try {
      store(s, out) match {
        case Left(ex) => Left(ex)
        case Right(s) => Right(s)
      }
    }
    catch {
      case ex: IOException => Left(ex)
    }
    finally {
      out.close()
    }
  }

  /**
   * Stores a game state into an output stream
   */
  def store(s: ReplayableGameState, out: OutputStream): Either[IOException, Unit] = {
    try {
      val objOut = new ObjectOutputStream(out)
      objOut.writeObject(s.history)
      Right(())
    }
    catch {
      case ex: IOException => Left(ex)
    }
  }

  /**
   * Loads a game state from a file
   */
  def load(f: File): Either[IOException, ReplayableGameState] = {
    val in = new GZIPInputStream(new BufferedInputStream(new FileInputStream(f)))
    try {
      load(in) match {
        case Left(ex) => Left(ex)
        case Right(s) => Right(s)
      }
    }
    catch {
      case ex: IOException => Left(ex)
    }
    finally {
      in.close()
    }
  }

  /**
   * Loads a game state from an input stream
   */
  def load(in: InputStream): Either[IOException, ReplayableGameState] = {
    try {
      val result = new ReplayableGameState()
      val objIn = new ObjectInputStream(in)
      result.history ++= objIn.readObject.asInstanceOf[ArrayBuffer[GameStateAction]]
      Right(result)
    }
    catch {
      case ex: IOException => Left(ex)
    }
  }

}